package com.imb.common.web.aop;

import cn.hutool.extra.servlet.ServletUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.imb.common.core.annotation.ApiLog;
import com.imb.common.core.utils.AddressUtils;
import com.imb.common.core.utils.JsonUtils;
import com.imb.common.mybatis.entity.ImbLog;
import com.imb.common.mybatis.mapper.ImbLogMapper;
import com.imb.common.web.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ExecutorService;

/**
 * @author xzhi
 * @desc 操作日志记录处理
 * @date 2024-09-23
 */
@Aspect
@Component
@Slf4j(topic = "api-log")
public class LogAspect {

    @Resource
    private ImbLogMapper sysLogMapper;
    @Resource
    private LogFilterExecutoServer logFilterExecutoServer;
    private final ThreadLocal<StopWatch> stopWatchThreadLocal = new ThreadLocal<StopWatch>();
    private final ThreadLocal<String> requestId = new ThreadLocal<>();

    @Pointcut("@annotation(com.imb.common.core.annotation.ApiLog)")
    public void ApilogPointCut() {}

    @Before(value = "ApilogPointCut()")
    public void befores(){
        requestId.set(IdWorker.getIdStr());
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        stopWatchThreadLocal.set(stopWatch);
    }

    @AfterReturning(value = "ApilogPointCut()",returning = "var")
    public void afterReturning(JoinPoint joinPoint, Object var) {
        apiLog(joinPoint, null, var);
    }

    @AfterThrowing(pointcut = "ApilogPointCut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,  Exception e) {
        apiLog(joinPoint, e, null);
    }

    protected  void apiLog(final JoinPoint joinPoint, final Exception es, Object methodResult){
        StopWatch stopwatch = stopWatchThreadLocal.get();
        try {
            ServletRequestAttributes sra = ServletUtils.getRequestAttributes();
            HttpServletRequest request = sra.getRequest();
            //获取全部Parameter参数
            Map<String, String[]> parameterMap = request.getParameterMap();
            // 获得注解
            ApiLog controllerLog = getAnnotationApiLog(joinPoint);
            if (controllerLog == null) {
                return;
            }
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            String className = joinPoint.getTarget().getClass().getName();
            Method method = ms.getMethod();
            String methodName=method.getName();
            String request_method=className+"."+methodName+"()";
            String params= Arrays.toString(joinPoint.getArgs());
            String returnStr= JsonUtils.toJsonString(methodResult);
            stopwatch.stop();
            double timeConsuming = stopwatch.getTotalTimeSeconds();
            double timeMins=stopwatch.getTotalTimeMillis();
            ExecutorService init = logFilterExecutoServer.init();
            if(init==null){
                this.logAdd(controllerLog,request,es,request_method,params,parameterMap,returnStr,timeMins);
            }else{
                init.execute(()->this.logAdd(controllerLog,request,es,request_method,params,parameterMap,returnStr,timeMins));
            }

            log.info("【请求ID】: {}" , requestId.get());
            log.info("【请求方法为】：{}" , request_method);
            log.info("【请求参数为(body)】：{}" , params);
            log.info("【请求参数为(param)】：{}",JsonUtils.toJsonString(parameterMap));
            log.info("【返回内容为】：{}" ,returnStr);
            log.info("【耗时(s)】:{}s -【耗时(ms)】:{}ms" ,timeConsuming,timeMins);
        }catch (Exception e){
            // 记录本地异常日志
            log.error("参数获取失败:{}", e.getMessage());
            e.printStackTrace();
        }finally {
            requestId.remove();
            stopWatchThreadLocal.remove();
        }
    }

    /**
     * 操作日志
     * @param controllerLog
     * @param request
     * @param es
     * @param request_method
     * @param params
     * @param parameterMap
     * @param returnStr
     * @param timeMins
     */
    public void logAdd(ApiLog controllerLog, HttpServletRequest request,final Exception es,
                       String request_method,String params,Map<String, String[]> parameterMap,String returnStr, double timeMins){
        ImbLog sysLog=new ImbLog();
        sysLog.setUsername("1");
        sysLog.setType(controllerLog.type().getInfo());
        sysLog.setOperation(controllerLog.method());
        sysLog.setIp(ServletUtil.getClientIP(request));
        sysLog.setRequest(request.getMethod());
        sysLog.setStatus("OK");
        if(es!=null){
            sysLog.setStatus("NO");
            String msg=es.getMessage();
            sysLog.setErrorMsg(msg!=null&&msg.length()>2000?msg.substring(0,2000):msg);
        }
        sysLog.setDict(AddressUtils.getRealAddressByIP(sysLog.getIp()));
        sysLog.setRequestDict(request.getRequestURI());
        sysLog.setMethod(request_method);
        sysLog.setParam(params);
        if(StringUtils.isBlank(params)){
            sysLog.setParam(JsonUtils.toJsonString(parameterMap));
        }
        sysLog.setResponse(returnStr);
        sysLog.setTime(timeMins+"(ms)");
        sysLog.setCreateTime(LocalDateTime.now());
        sysLogMapper.insert(sysLog);
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private ApiLog getAnnotationApiLog(JoinPoint joinPoint)
    {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null)
        {
            return method.getAnnotation(ApiLog.class);
        }
        return null;
    }
}
